Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

97장. 도메인 설계 — 3개 서비스로 쪼개기

이 장에서 말하고자 하는 것

지금까지 우리는 AWS의 부품을 하나씩 살펴봤다.

이제 마지막 단원은

“한 서비스를 어떻게 마이크로서비스 3개로 쪼개는가”

를 구체적으로 다룬다.

이 장은 도메인 분리 의 기준과 결과를 정리한다.


1. 시작 — 모놀리스의 하루

작은 쇼핑 서비스를 가정하자.

사용자
  ├─ 회원가입 / 로그인
  ├─ 상품 둘러보기
  ├─ 장바구니
  ├─ 주문
  ├─ 결제
  └─ 주문 내역 보기

처음에는 한 코드베이스 · 한 DB로 충분하다.

[모놀리스]
  └─ RDS (users + products + orders + payments)

문제는 자라면서 시작된다.

  • 결제 모듈 변경이 회원가입 배포와 묶임
  • 한 팀이 다른 영역을 모르고 건드림
  • DB 한 곳에 부하 누적

2. 분리의 기준 — 도메인 경계

마구잡이로 쪼개면 더 큰 혼란이 된다.

“독립적으로 변할 수 있는 단위” 로 나눈다

판단 기준:

  • 데이터 소유권 — 누가 이 데이터의 진실을 책임지나
  • 변경 주기 — 함께 자주 바뀌나, 따로 바뀌나
  • 팀 / 책임 — 누가 운영하나
  • 외부 의존성 — 외부 API · 정책에 묶이나

이 책에서는 다음 세 도메인으로 나눈다.

1. users      (회원 · 인증)
2. orders     (주문 · 장바구니)
3. payments   (결제)

각각이 위 기준에서 독립적이다.


3. 각 서비스의 역할

users

  • 회원가입 / 로그인 / 프로필
  • JWT 발급 (Cognito 와 함께)
  • DB: 사용자 테이블

orders

  • 장바구니 · 주문 생성 · 주문 조회
  • “사용자 정보” 는 API로 또는 이벤트로 받아 일부만 저장
  • DB: 주문 / 장바구니 테이블

payments

  • 결제 시도 · 환불 · 결제 내역
  • 외부 PG와 통신
  • DB: 결제 트랜잭션 (감사 강화)

“주문 데이터를 누가 책임지나” 같은 질문에 한 서비스만 답해야 한다


4. 데이터를 어떻게 공유할까

70장에서 본 세 가지 패턴.

orders 가 "사용자 이름" 이 필요할 때:

① 동기 호출:  orders → users API
② 이벤트 + 로컬 복제:
   users 가 "UserCreated/Updated" 발행
   orders 가 받아서 자기 DB에 (id, name) 만 저장
③ 별도 BFF / CQRS

이 책의 척추 구조는 주로 ②를 권장한다.

orders DB 안에:
  user_id_name_cache (id, name) — 화면용
실제 진실은 users 서비스

5. 서비스 간 호출

orders → users (사용자 확인)              : 동기
orders → payments (결제 시도)             : 동기 (응답 필요)
orders → SNS "OrderCreated"                : 비동기
   ├─ notification 워커 → 알림
   ├─ analytics 워커  → 통계
   └─ inventory 워커  → 재고 차감
  • 사용자에게 응답이 필요한 흐름 → 동기
  • 부수 효과 → 비동기

6. 같은 패턴, 다른 인스턴스

각 서비스가 같은 인프라 패턴을 반복한다.

서비스 = {
  ECR 리포지토리
  ECS Task Definition + Service
  ALB Target Group + Listener Rule
  IAM Task Role (좁은 권한)
  RDS 또는 DynamoDB
  Secrets Manager 항목
  CloudWatch Log Group
  CloudWatch Alarm 묶음
}

Terraform 모듈 하나로 추상화하면 새 서비스 추가가 단 몇 줄.

module "users"    { source = "./modules/microservice"  name = "users"   ... }
module "orders"   { source = "./modules/microservice"  name = "orders"  ... }
module "payments" { source = "./modules/microservice"  name = "payments" ... }

7. 경계의 비용 — 분리는 공짜가 아니다

마이크로서비스는 다음 비용이 든다.

  • 네트워크 호출 지연
  • 분산 트랜잭션의 어려움 (Saga · 멱등성 · Outbox)
  • 운영 도구의 추가 (관측성 · IAM · 배포)
  • DB 데이터 중복 (의도적)
  • 개발 환경 복잡도

작게 시작해서 경계가 보이는 곳부터 쪼개는 게 가장 안전하다
처음부터 5개 · 10개 서비스로 쪼개는 건 거의 항상 후회로 이어진다


8. 이 책에서 3개로 끝낸 이유

  • 도메인 경계가 명확
  • 한 권에서 다룰 수 있는 폭
  • 한 사람 / 작은 팀이 운영 가능

진짜 서비스가 자라면 다음으로 자연스럽게 분리되는 후보들:

notification, search, recommendation, inventory, audit, ...

같은 패턴을 그대로 복제하면 된다.


9. 우리 서비스의 최종 도식

[사용자]
  ↓ DNS (Route 53)
[CloudFront] (WAF, us-east-1 ACM, OAC)
 ├─ /static/* → S3 static (CloudFront + OAC)
 └─ /api/*    → API Gateway (JWT Authorizer · Rate limit)
                  ↓ VPC Link
              [Private ALB] (서울 ACM)
                 ├─ /api/users/*    → ECS "users"    → RDS users-db
                 ├─ /api/orders/*   → ECS "orders"   → RDS orders-db
                 └─ /api/payments/* → ECS "payments" → RDS payments-db

부수 효과:
  ECS "orders" → SNS "order-events"
                  ├─ SQS notification → ECS notification-worker
                  ├─ SQS analytics    → ECS analytics-worker
                  └─ SQS inventory    → ECS inventory-worker

이 그림이 1~96장의 결과물이고
다음 장(98) 이 그 흐름을 한 번 더 완성형으로 그린다.


10. 직접 점검 — 도메인 경계가 잘 잡혔는지

다음 질문에 명확히 답하면 분리가 잘된 것이다.

  • 이 서비스의 데이터 진실은 누구인가
  • 다른 서비스가 이 데이터를 어떻게 받는가 (API / 이벤트 / 복제)
  • 이 서비스 혼자 배포할 수 있는가
  • 이 서비스 장애가 다른 서비스에 어디까지 영향을 주는가
  • 한 명이 이 서비스를 처음부터 끝까지 운영할 수 있는가

11. 코드로는 이렇게 생겼다 — 서비스 모듈 (스케치)

module "users" {
  source = "./modules/microservice"

  name              = "users"
  container_image   = "${var.ecr_url}/users:${var.image_tag}"
  container_port    = 8080
  cpu               = 512
  memory            = 1024
  desired_count     = 2
  alb_listener_arn  = module.shared.alb_listener_arn
  path_pattern      = "/api/users/*"

  db = {
    engine         = "postgres"
    instance_class = "db.t4g.small"
    multi_az       = true
  }

  task_role_extra_policies = [
    aws_iam_policy.users_sns_publish.arn,
  ]
}
  • 모듈은 70~80줄의 Terraform
  • 인스턴스마다 위처럼 10여 줄

12. 이렇게 쓰면 망한다 — 안티패턴

안티패턴 1. 기능 단위가 아닌 기술 계층으로 나눈다

“API 서비스 / 비즈니스 서비스 / DB 서비스” 식 분리는 결합도가 폭증한다.

도메인(사람이 알아보는 비즈니스 단위) 으로 나눈다

안티패턴 2. 작은 단위로 너무 잘게 쪼갠다

“마이크로” 가 아니라 “나노” 서비스 — 호출 지옥.

한 서비스가 한 화면의 한 영역 전체를 책임질 수 있게

안티패턴 3. 분리해 놓고 같은 DB를 공유

이름만 분리, 실질은 모놀리스.

Database per Service 가 진짜 분리

안티패턴 4. 처음부터 모든 부수 효과를 동기로 묶는다

한 서비스 장애가 모든 서비스로 번진다.


13. 한 줄로 정리

도메인 분리는 “독립적으로 변할 수 있는 단위” 로 나누는 일이며,
데이터 소유 · 변경 주기 · 책임 · 외부 의존이 그 기준이다


14. 이 장의 핵심 정리

  1. 도메인 경계가 명확한 곳부터 나눈다.
  2. 이 책은 users · orders · payments 의 3 서비스로 끝낸다.
  3. 같은 인프라 패턴을 Terraform 모듈로 추상화해 반복한다.
  4. 외부 응답은 동기, 부수 효과는 비동기.
  5. 분리는 비용이 든다 — 작게 시작하고 경계가 보일 때 쪼갠다.
  6. 다음 장은 이 세 서비스가 합쳐진 전체 흐름을 한 번 더 정리한다.